package awesome.cpu.structure;

import awesome.cpu.gui.GUIOutput;
import awesome.cpu.utils.Mode;

public class Memory extends NonSequentialComponent {
	
	private static boolean outputDebug = false;
	
	public final static int KEY_MEM_READ = 1;
	private int mMemRead;
	public final static int KEY_MEM_WRITE = 2;
	private int mMemWrite;
	
	public final static int KEY_MEM_ADDR = 3;
	private int mMemAddr;
	public final static int KEY_WRITE_DATA = 4;
	private int mWriteData;
	
	public final static int KEY_READ_DATA = 5;
	//private int mReadData;

	private final static int MEM_SIZE = 1024;
	private byte mData[] = new byte[MEM_SIZE];
	
	private int mProgramSize = 0;
	
	private final static Memory mInstance = new Memory();
	
	private Memory() {}

	public static Memory getInstance() {
		return mInstance;
	}

	@Override
	boolean setData(int keyInterface, int data) {
		switch (keyInterface) {
		case KEY_MEM_READ:
			mMemRead = data;
			return true;
		case KEY_MEM_WRITE:
			mMemWrite = data;
			write();
			return true;
		case KEY_MEM_ADDR:
			mMemAddr = data;
			write();
			return true;
		case KEY_WRITE_DATA:
			mWriteData = data;
			write();
			return true;
		}
		return false;
	}

	@Override
	int getData(int keyInterface) {
		BusMonitor.transferData(Control.getInstance(), Control.KEY_MEM_READ,
				this, KEY_MEM_READ);
		BusMonitor.transferData(Control.getInstance(), Control.KEY_MEM_WRITE,
				this, KEY_MEM_WRITE);
		BusMonitor.transferData(RegisterC.getInstance(), RegisterC.KEY_REGC_OUT,
				this, KEY_WRITE_DATA);
		BusMonitor.transferData(MuxPC2M.getInstance(), MuxPC2M.KEY_SELECTED,
				this, KEY_MEM_ADDR);
		
		if (outputDebug && Mode.getDebugLevel() >= Mode.DEBUG_LEVEL_PRINT_ALL) {
			System.out.println("Memory: Address = "+mMemAddr);
			
		}
		
		switch (keyInterface) {
		case KEY_READ_DATA:
			return read();
		}
		return 0;
	}

	/**
	 * Called <b>BEFORE</b> executing the instructions.
	 */
	public void loadProgram(byte[] instructions) {
		mProgramSize = instructions.length;
		for (int i = 0; i < mProgramSize; i++) mData[i] = instructions[i];
		if (outputDebug && Mode.getDebugLevel() >= Mode.DEBUG_LEVEL_PRINT_ALL) {
			GUIOutput.println(mProgramSize/4 + " instruction(s) loaded.");
			GUIOutput.println("program size = "+mProgramSize);
			System.out.println(mProgramSize/4 + " instruction(s) loaded.");
			System.out.println("program size = "+mProgramSize);
		}
		
		for (int i = mProgramSize; i < mProgramSize + 100; i ++)
			mData[i] = '6';
		
	}
	
	int getDataOffset() {
		return mProgramSize;
	}
	
	private int read() {
		int read = 0;
		switch (mMemRead) {
		case 1:
			read = mData[mMemAddr];
			if (outputDebug && Mode.getDebugLevel() >= Mode.DEBUG_LEVEL_PRINT_ALL) {
				System.out.println("Memory: read ["+mMemAddr+"] = "+read);
			}
			break;
		case 2:
			read = (int)(mData[mMemAddr+3] & 0xff) << 24
					| (int)(mData[mMemAddr+2] & 0xff) << 16
					| (int)(mData[mMemAddr+1] & 0xff) << 8
					| (int)(mData[mMemAddr] & 0xff);
			if (outputDebug && Mode.getDebugLevel() >= Mode.DEBUG_LEVEL_PRINT_ALL) {
				System.out.println("Memory: read ["+mMemAddr+"..."+(mMemAddr+3)+"] = "+read);
			}
			break;
		}
		return read;
	}
	
	private void write() {
		switch (mMemWrite) {
		case 0:
			break;
		case 1:
			mData[mMemAddr] = (byte)mWriteData;
			if (outputDebug && Mode.getDebugLevel() >= Mode.DEBUG_LEVEL_PRINT_ALL)
				System.out.println("Memory write byte " + (byte)mWriteData);
			break;
		case 2:
			mData[mMemAddr] = (byte)mWriteData;
			mData[mMemAddr+1] = (byte)(mWriteData >>> 8);
			mData[mMemAddr+2] = (byte)(mWriteData >>> 16);
			mData[mMemAddr+3] = (byte)(mWriteData >>> 24);
			
			if (outputDebug && Mode.getDebugLevel() >= Mode.DEBUG_LEVEL_PRINT_ALL)
				System.out.println("Memory write word " + (byte)mWriteData + "+" + (byte)(mWriteData >>> 8) + "+" +
						(byte)(mWriteData >>> 16) + "+" + (byte)(mWriteData>>>24));
			
			break;
		}
	}

}
